/*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 ("License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.zimbra.com/license
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is: Zimbra AJAX Toolkit.
*
* The Initial Developer of the Original Code is Zimbra, Inc.
* Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
* All Rights Reserved.
*
* Portions of the original code have been adapted for CLDC 1.0 and MIDP 2.0 by CalemEAM Inc. 
* Portions created by CalemEAM are Copyright (C) 2008 CalemEAM Inc. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK *****
*/

package com.calemeam.text;

//
//

/**
 * @param pattern       The number pattern.
 * @param skipNegFormat Specifies whether to skip the generation of this
 *                      format's negative value formatter. 
 *                      <p>
 *                      <strong>Note:</strong> 
 *                      This parameter is only used by the implementation 
 *                      and should not be passed by application code 
 *                      instantiating a custom number format.
 */
public class MicroNumberFormat extends MicroFormat {
   //Do not support: E
   private static final String META_CHARS = "0#.,"; //original: "0#.,E"
   
   /**
    * Negative formatter.
    */
   private MicroNumberFormat negativeFormatter;
   /**
    * Grouping offset
    */
   protected int groupingOffset;
   /**
    * Min/max digits
    */
   protected int minIntDigits;
   /**
    * Min/max fraction
    */
   protected int minFracDigits;
   protected int maxFracDigits;
   
   /**
    * Is the format percent?
    */
   protected boolean isPercent;
   
   /**
    * Class constructor
    */ 
   public MicroNumberFormat(String pattern) {
      super(pattern);
      String pattern2=null;
      int idx=pattern.indexOf(";");
      if (idx !=-1) {
         pattern2=pattern.substring(idx+1);
         pattern=pattern.substring(0, idx);
      }
      // parse prefix
      StringBuffer sb=new StringBuffer();
      int i = this.parseStatic(pattern, 0, sb);
      boolean hasPrefix=sb.length()>0;
      if (hasPrefix) {
         this.segments.addElement(new MicroTextSegment(this, sb.toString()));
      }

      // parse number descriptor
      int start = i;
      while (i < pattern.length() && META_CHARS.indexOf(pattern.charAt(i)) != -1) {
         i++;
      }
      int end = i;
      String numPattern = pattern.substring(start, end);
      int dot = numPattern.indexOf('.');
      String whole = (dot != -1) ? numPattern.substring(0, dot) : numPattern;
      int zero;
      if (whole!=null) {
         int comma = whole.lastIndexOf(',');
         if (comma != -1) {
            this.groupingOffset = whole.length() - comma - 1;
         }
         zero = whole.indexOf("0");
         if (zero != -1) {
            this.minIntDigits = whole.length() - zero;
         }
      }
      String fract= (dot != -1) ? numPattern.substring(dot + 1) : null;
      if (fract!=null) {
         zero = fract.lastIndexOf('0');
         if (zero != -1) {
            this.minFracDigits = zero + 1;
         }
         this.maxFracDigits = MicroFormatUtil.includeChars(fract, "#0").length();
      }

      this.segments.addElement(new NumberSegment(this, numPattern));

      // parse suffix
      sb.setLength(0);
      i = this.parseStatic(pattern, i, sb);
      if (sb.length()>0) {
         this.segments.addElement(new MicroTextSegment(this, sb.toString()));
      }

      if (pattern2!=null) {
         this.negativeFormatter = new MicroNumberFormat(pattern2);
      }
   }
   
   //#if Debug
//#    public String toString() {
//#       StringBuffer sb=new StringBuffer();
//#       sb.append("MicroNumberFormat: ").append(super.toString());
//#       if (this.negativeFormatter!=null) {
//#          sb.append(", negativeFormatter=").append(this.negativeFormatter);
//#       }
//#       sb.append("isPercent=").append(this.isPercent).append(";");
//#       sb.append("groupingOffset=").append(this.groupingOffset).append(";");   
//#       sb.append("minDigit=").append(this.minIntDigits).append(";");
//#       sb.append("minDecimal=").append(this.minFracDigits).append(";");
//#       sb.append("maxDecimal=").append(this.maxFracDigits);
//#       sb.append("]");
//#       return sb.toString();
//#    };
   //#endif
   
   /**
    * Removed perMill and currency support
    * Not supporting non-meta chars.
    */
   private int parseStatic(String s, int i, StringBuffer sb) {
      while (i < s.length()) {
         char c = s.charAt(i++);
         if (META_CHARS.indexOf(c) != -1) {
            i--;
            break;
         }
         if (c=='%') {
            sb.append(MicroFactory.getRes().get(MicroResIf.percent_key));
            this.isPercent=true;
         } else {
            sb.append(c);
         }
      }
      return i;
   }

   // Static functions
   public static MicroNumberFormat getInstance(String format) {
      MicroNumberFormat mnf=(MicroNumberFormat)FORMATTERS.get(format);
      if (mnf==null) {
         mnf=new MicroNumberFormat(format);
         FORMATTERS.put(format, mnf);
      }
      return mnf;
   }
   
   /**
    * Format int
    */
   public String format(int i) {
      String st=Integer.toString(Math.abs(i));
      if (i < 0 && this.negativeFormatter!=null) {
         return this.negativeFormatter._format(st);
      }
      String nst=_format(st);
      return (i<0) ? MicroFactory.getRes().get(MicroResIf.minus_key) + nst : nst;
   };
   
   /**
    * Format a string based on the format specification. It uses negative format if available.
    * It also handles percent formatting by manipulating decimal position.
    * <p>
    * Systems with double/float or fixed point have to convert the number to decimal format and 
    * use this function to format.
    * </p>
    */
   public String formatNumberStr(String numberStr) {
      //To cover null cases.
      if (numberStr==null || numberStr.length()==0) return "";
      //Handle negative case
      boolean neg=false;
      if (numberStr.startsWith("-")) {
         neg=true;
         numberStr=numberStr.substring(1);
      }
      //Handle percent
      if (this.isPercent) {
         numberStr=multiplyBy100(numberStr);
      }
      if (neg && this.negativeFormatter!=null) {
         return this.negativeFormatter._format(numberStr);
      }
      String nst=_format(numberStr);
      return neg ? MicroFactory.getRes().get(MicroResIf.minus_key) + nst : nst;
   };   

   //
   // AjxNumberFormat.NumberSegment class
   //
   protected class NumberSegment extends MicroSegment {
      public NumberSegment(MicroNumberFormat format, String seg) {
         super(format, seg);
      }
      //#if Debug
//#       public String toString() {
//#          return "number:" +this.seg;
//#       }
      //#endif

      // Public methods
      public String format(Object number) {
         String s=(String)number;
         return this.normalize(s);
      };

      // Protected methods
      protected String normalize(String s) {
         //#if Debug
//#          System.out.println("start normalize, s="+s);
         //#endif
         //Only handle decimal
         int index=s.indexOf(".");
         //#if Debug
//#          System.out.println("s="+s+", dec index="+index);
         //#endif
         String whole= index==-1 ? s : s.substring(0, index);
         String fract= index==-1 ? null : s.substring(index+1);
         //#if Debug
//#          System.out.println("whole="+whole+", fract="+fract);
         //#endif
         StringBuffer sb=new StringBuffer();
         //Group the digits first.
         if (whole.length() > 0) {
            //Make sure we have a grouping offset here.
            if (groupingOffset>0 && whole.length() > groupingOffset) {
               int i = whole.length() - groupingOffset;
               while (i > 0) {
                  sb.insert(0, whole.substring(i, i+groupingOffset));
                  sb.insert(0, MicroFactory.getRes().get(MicroResIf.grouping_key));
                  i -= groupingOffset;
               }
               sb.insert(0, whole.substring(0, i + groupingOffset));
            } else {
               sb.append(whole);
            }
         }
         //#if Debug
//#          System.out.println("grouping portion="+whole);
         //#endif
         // normalize fraction portion.
         if (fract!=null && fract.length()>0) {
            //Add decimal
            sb.append(MicroFactory.getRes().get(MicroResIf.decimal_key));
            if (fract.length() < minFracDigits) {
               fract = MicroFormat.zeroPad(fract, minFracDigits,MicroFactory.getRes().get(MicroResIf.zero_key), true);
            }
            if (fract.length() > maxFracDigits) {
               sb.append(fract.substring(0, maxFracDigits-1));
               //@todo - to use arithmetic to carry the overflow to the left including whole portion.
               if (fract.charAt(maxFracDigits) > '4' && fract.charAt(maxFracDigits-1) < '9') {
                  sb.append((char)(fract.charAt(maxFracDigits-1)+1));
               } else {
                  sb.append(fract.charAt(maxFracDigits-1));
               }
            } else {
               sb.append(fract);
            }
         } else {
            if (minFracDigits > 0) {
               sb.append(MicroFactory.getRes().get(MicroResIf.decimal_key));
               for (int i=0; i< minFracDigits; i++) sb.append(MicroFactory.getRes().get(MicroResIf.zero_key));
            }
         }
         return sb.toString();
      }
   }
   
   /**
    * Parsing function is limited and is not based on the patterns above: <br>
    * <ul>
    * <li> Parse only digits, decimal, grouping, and sign
    * <li> Sign can only be at the beginning
    * <li> No other formats are accepted (no exponential, no currency symbol, no negative format)
    * <ul>
    */
   //#if ParsingConfiguration
//#    public static int parseInt(String intStr) throws MicroParsingException, NumberFormatException {
//#       return Integer.parseInt(toNumberString(intStr).getNormalized());
//#    }
   //#endif
   
   /**
    * Parse a formatted number string to internal string format that can be used by Java's parse functions
    * (parseInt, parseFloat, parseDouble or a custom fixed point parser).
    * <p>
    * Currency parsing is not supported. Use number parsing instead.
    * </p>
    * @return a valid string that can be parsed to a number
    */
   //#if ParsingConfiguration
//#    public static String parseNumber(String numberStr) throws MicroParsingException, NumberFormatException {
//#       return toNumberString(numberStr).getNormalized();
//#    }
   //#endif
   
   /**
    * Parse a formatted percent string and divide by 100
    * @return a valid nubmer string that can be parsed to a number.
    */
   //#if ParsingConfiguration
//#    public static String parsePercent(String percentStr) throws MicroParsingException, NumberFormatException {
//#       return divideBy100(toNumberString(percentStr, MicroFactory.getRes().get(MicroResIf.percent_key)).getNormalized());
//#    }
   //#endif

   /**
    * Parse a number string includinge locale based grouping and decimal, sign and space.
    * @param st
    */
   //#if ParsingConfiguration
//#    protected static NumberParsed toNumberString(String st) throws MicroParsingException {
//#       return toNumberString(st, "");
//#    }
   //#endif
   
   //#if ParsingConfiguration
//#    protected static NumberParsed toNumberString(String st, String extra) throws MicroParsingException {
//#       NumberParsed np=new NumberParsed();
//#       //Remove space and grouping
//#       String nst=MicroFormatUtil.excludeChars(st, " "+MicroFactory.getRes().get(MicroResIf.grouping_key)+extra);
//#       //Get sign if any
//#       if (nst.startsWith("-")) {
//#          np.isNegative=true;
//#          nst=nst.substring(1);
//#       }
//#       //Verify that the string consists of only digits and decimal separator
//#       String dec=MicroFactory.getRes().get(MicroResIf.decimal_key);
//#       String tmp=MicroFormatUtil.includeChars(nst, "0123456789"+dec);
//#       if (!nst.equals(tmp)) {
//#          throw new MicroParsingException("Unknown chars in number string: "+nst);
//#       }
//#       //Split into whole and fract
//#       int idx=nst.indexOf(dec);
//#       if (idx==-1) {
//#          np.whole=nst;
//#          np.fract=null;
//#       } else {
//#          if (idx==0) np.whole="0";
//#          else np.whole=nst.substring(0, idx);
//#          np.fract=nst.substring(idx+1);
//#       }
//#       return np;      
//#    }
   //#endif
   
   /**
    * Multiply a decimal string by 100
    */
   public static String multiplyBy100(String numberStr) {
      if (numberStr.equals("0")) return numberStr;
      StringBuffer sb=new StringBuffer();
      int idx=numberStr.indexOf(".");
      if (idx==-1) return sb.append(numberStr).append("00").toString();
      if (!(idx==1 && numberStr.startsWith("0"))) {
         sb.append(numberStr.substring(0, idx)); //whole portion
      }
      if (numberStr.length()<=idx+3) {//1 or 2 decimal digits
         sb.append(numberStr.substring(idx+1));
         if (numberStr.length()==idx+2) sb.append("0");
      } else {//more than 2 decimal digits
         sb.append(numberStr.substring(idx+1, idx+3));
         sb.append(".").append(numberStr.substring(idx+3));
      }
      return sb.toString();
   }
   
   /**
    * Divide a decimal string by 100
    */
   //#if ParsingConfiguration
//#    public static String divideBy100(String numberStr) {
//#       if (numberStr.equals("0")) return numberStr;
//#       StringBuffer sb=new StringBuffer();
//#       String whole;
//#       int idx=numberStr.indexOf(".");
//#       if (idx==-1) whole=numberStr;
//#       else {
//#          whole= idx==0? "0" : numberStr.substring(0, idx);
//#          sb.append(numberStr.substring(idx+1));
//#       }
//#       if (whole.length()<3) {
//#          sb.insert(0, whole);
//#          if (whole.length()==1) sb.insert(0, "0");
//#          sb.insert(0, "0.");
//#       } else {
//#          sb.insert(0, whole.substring(whole.length()-2));
//#          sb.insert(0, ".").insert(0, whole.substring(0, whole.length()-2));
//#       }
//#       return sb.toString();
//#    }
   //#endif
}